進入.Net core後,C#大量使用非同步方法,像是從使用EF core從DB取得資料。我們經常會遇到當API接收到request後,需要針對request的資料做驗證,有時候需要跟DB的資料做比對,對Task物件的使用就非常重要。
我們來設計一個簽到系統,當輸入名字後會撈取名單,確認簽到的人在名單後,把出席紀錄寫到資料庫中。
// 先準備好要呼叫database的方法
public Task<Member?> GetMemberByNameAsync(string name) => // todo
public Task<Attendance?> CreateAttendanceAsync(Attendance attendance) => // todo
// API的殼
public Task<IActionResult> CheckIn(string name)
{
var result = GetMemberByNameAsync(name)
// todo
}
首先觀察GetMemberByNameAsync
方法的回傳值,其實可以視為Task<Option<Member>>
,我們希望第一步查詢資料庫中是否有這個名字,先試試使用Map去組合接下來的步驟,為了跟接下來的nullable做區別,這邊就叫它TaskMap
var result = GetMemberByNameAsync(name)
.TaskMap(member=> // todo
目前的member型別為Member?,我希望映射到另一個Attendance的Entity,並且會被nullable包裹
var result = GetMemberByNameAsync(name)
.TaskMap(member => member
.Map(m => new Attendance{ name = m.Name })
// todo
回傳的型別是Attendance?,這邊一樣使用Map來套用c
var result = GetMemberByNameAsync(name)
.TaskMap(member => member
.Map(m => new Attendance{ name = m.Name })
.Map(CreateAttendanceAsync))
這時候result的型別會是Task<Task<….>>,有點奇怪是因為我們使用了TaskMap,而CreateAttendanceAsyn的回傳值是一個Task物件,所以這時候要改成使用Bind
var result = GetMemberByNameAsync(name)
.TaskBind(member => member
.Map(m => new Attendance{ name = m.Name })
.Map(CreateAttendanceAsync))
接下來await就可以取得值了
var result = await GetMemberByNameAsync(name)
.TaskBind(member => member
.Map(m => new Attendance{ name = m.Name })
.Map(CreateAttendanceAsync));
最後要做一個處裡,如果是null就回傳失敗,完整的api就會變成
public Task<IActionResult> CheckIn(string name)
{
return (await GetMemberByNameAsync(name)
.TaskBind(member => member
.Map(m => new Attendance{ name = m.Name })
.Map(CreateAttendanceAsync)))
switch
{
{} => Ok(),
null => BadRequest()
}
}
在設計的時候為了避免套嵌經常使用Bind來連接,因為很常遇到連接的方法回傳值具有相同的”盒子”,以非同步設計來說後續的方法也經常是非同步的。碎歲念一下,寫到這裡我真的好想放棄,已經不知道自己在寫什麼了,也不知道接下來要寫什麼嗚嗚嗚。